/*
 * Copyright (c) 2023 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
#include "response_apdu.h"

#include <securec.h>
#include <stddef.h>

#include "apdu_core_defines.h"
#include "apdu_utils.h"
#include "logger.h"
#include "response_apdu_inner.h"

#define RES_BUFF_TRAILER_LEN sizeof(uint16_t) // the response trailer sw1 sw2 length
#define RES_BUFF_MIN_LEN RES_BUFF_TRAILER_LEN // the response trailer len
#define RES_BUFF_MAX_LEN (RES_BUFF_TRAILER_LEN + MAX_APDU_RESP_SIZE)

#define APDU_UINT8_WIDTH 8

ResponseApdu *CreateResponseApdu(uint32_t bodyLength)
{
    if (bodyLength > MAX_APDU_DATA_SIZE) {
        LOG_ERROR("not support now, bodyLength = %u", bodyLength);
        return NULL;
    }

    uint32_t allocSize = sizeof(ResponseApdu) + bodyLength + RES_BUFF_TRAILER_LEN;
    ResponseApdu *out = malloc(allocSize);
    if (out == NULL) {
        LOG_ERROR("malloc error");
        return NULL;
    }

    if (memset_s(out, allocSize, 0, allocSize) != EOK) {
        free(out);
        LOG_ERROR("memset error");
        return NULL;
    }

    out->length = bodyLength + RES_BUFF_TRAILER_LEN;
    return out;
}

bool CheckResponseApdu(const ResponseApdu *responseApdu)
{
    if (responseApdu == NULL) {
        LOG_ERROR("invalid input");
        return false;
    }
    if (responseApdu->length < RES_BUFF_MIN_LEN || responseApdu->length > RES_BUFF_MAX_LEN) {
        LOG_ERROR("invalid length = %u", responseApdu->length);
        return false;
    }
    return true;
}

void DestroyResponseApdu(ResponseApdu *apdu)
{
    if (apdu == NULL) {
        return;
    }
    uint32_t allocSize = sizeof(ResponseApdu) + apdu->length;
    (void)memset_s(apdu, allocSize, 0, allocSize);
    free(apdu);
}

bool GetStatusWords(const ResponseApdu *responseApdu, uint16_t *statusWords)
{
    if (!CheckResponseApdu(responseApdu)) {
        LOG_ERROR("invalid input responseApdu");
        return false;
    }

    if (statusWords == NULL) {
        LOG_ERROR("invalid input statusWords");
        return false;
    }

    uint8_t sw1 = responseApdu->data[responseApdu->length - 2];
    uint8_t sw2 = responseApdu->data[responseApdu->length - 1];

    *statusWords = (uint16_t)(sw1 << APDU_UINT8_WIDTH) | (uint16_t)sw2;
    return true;
}

bool CheckStatusWords(const ResponseApdu *responseApdu, uint16_t statusWords)
{
    uint16_t sw = 0;
    if (!GetStatusWords(responseApdu, &sw)) {
        LOG_ERROR("GetStatusWords failed");
        return false;
    }
    bool result = (statusWords == sw);

    if (!result) {
        LOG_INFO("StatusWords is 0x%x", (uint32_t)sw);
    }
    return result;
}

const uint8_t *GetResponseDataBuffer(const ResponseApdu *responseApdu, uint32_t *length)
{
    if (!CheckResponseApdu(responseApdu)) {
        LOG_ERROR("invalid input responseApdu");
        return NULL;
    }

    if (length == NULL) {
        LOG_ERROR("invalid input length");
        return NULL;
    }

    *length = responseApdu->length - RES_BUFF_TRAILER_LEN;
    return responseApdu->data;
}

bool GetResponseUint8ValueAt(const ResponseApdu *responseApdu, uint32_t index, uint8_t *value)
{
    return GetResponseBufferValueAt(responseApdu, index, value, sizeof(uint8_t));
}

bool GetResponseUint16ValueAt(const ResponseApdu *responseApdu, uint32_t index, uint16_t *value)
{
    if (!GetResponseBufferValueAt(responseApdu, index, (uint8_t *)value, sizeof(uint16_t))) {
        return false;
    }

    DataRevert((uint8_t *)value, sizeof(uint16_t));
    return true;
}

bool GetResponseUint32ValueAt(const ResponseApdu *responseApdu, uint32_t index, uint32_t *value)
{
    if (!GetResponseBufferValueAt(responseApdu, index, (uint8_t *)value, sizeof(uint32_t))) {
        return false;
    }

    DataRevert((uint8_t *)value, sizeof(uint32_t));
    return true;
}

bool GetResponseBufferValueAt(const ResponseApdu *responseApdu, uint32_t index, uint8_t *value, uint32_t size)
{
    if (!CheckResponseApdu(responseApdu)) {
        LOG_ERROR("invalid input responseApdu");
        return false;
    }

    if (value == NULL || size == 0) {
        LOG_ERROR("invalid input value");
        return false;
    }

    uint32_t dataSize = responseApdu->length - RES_BUFF_TRAILER_LEN;

    if (index > dataSize) {
        LOG_ERROR("invalid index = %u, dataSize = %u", index, dataSize);
        return false;
    }

    if (size > dataSize) {
        LOG_ERROR("invalid size(%u) >  dataSize(%u)", size, dataSize);
        return false;
    }

    if (index + size > dataSize) {
        LOG_ERROR("invalid index(%u) + size(%u) > dataSize(%u)", index, size, dataSize);
        return false;
    }
    if (memcpy_s(value, size, responseApdu->data + index, size) != EOK) {
        LOG_ERROR("memory copy failed");
        return false;
    }
    return true;
}